package com.ecommerce.service.async;

import com.alibaba.fastjson.JSON;
import com.ecommerce.constant.GoodsConstant;
import com.ecommerce.dao.EcommerceGoodsDao;
import com.ecommerce.entity.EcommerceGoods;
import com.ecommerce.goods.GoodsInfo;
import com.ecommerce.goods.SimpleGoodsInfo;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 异步服务接口实现
 */
@Slf4j
@Service
@Transactional
public class AsyncServiceImpl implements AsyncService{

    private final StringRedisTemplate redisTemplate;

    private final EcommerceGoodsDao ecommerceGoodsDao;

    public AsyncServiceImpl(EcommerceGoodsDao ecommerceGoodsDao, StringRedisTemplate redisTemplate) {
        this.ecommerceGoodsDao = ecommerceGoodsDao;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 将商品信息保存到数据表, 更新商品缓存
     */
    @Async("getAsyncExecutor") // 异步任务需要加上注解, 并指定使用的线程池
    @Override
    public void asyncImportGoods(List<GoodsInfo> goodsInfos, String taskId) {
        log.info("async task running taskId: [{}]", taskId);

        StopWatch watch = StopWatch.createStarted();

        // 1. 如果goodsInfo中存在重复的商品, 不保存；直接返回, 记录错误日志
        // 请求数据是否合法的标记
        boolean isIllegal = false;

        // 将商品信息字段 joint 在一起, 用来判断是否存在重复
        Set<String> goodsJointInfos = new HashSet<>(goodsInfos.size());
        // 过滤出来的, 可以入库的商品信息(规则按照自己的业务需求自定义即可)
        List<GoodsInfo> filterGoodsInfo = new ArrayList<>(goodsInfos.size());

        // 循环，过滤非法参数与判定当前请求是否合法
        for (GoodsInfo goodsInfo: goodsInfos) {
            // 基本条件不满足的，直接过滤
            if (goodsInfo.getPrice() <= 0 || goodsInfo.getSupply() <= 0) {
                log.info("goods info is invalid: [{}]", JSON.toJSONString(goodsInfo));
                continue;
            }

            // 组合商品信息
            String jointInfo = String.format(
                    "%s,%s,%s",
                    goodsInfo.getGoodsCategory(),
                    goodsInfo.getBrandCategory(),
                    goodsInfo.getGoodsName()
            );
            if (goodsJointInfos.contains(jointInfo)) {
                isIllegal = true;
            }

            // 加入到两个容器中
            goodsJointInfos.add(jointInfo);
            filterGoodsInfo.add(goodsInfo);
        }

        // 如果存在重复商品或者没有需要入库的商品, 直接打印日志返回
        if (isIllegal || CollectionUtils.isEmpty(filterGoodsInfo)) {
            watch.stop();
            log.warn("import nothing: [{}]", JSON.toJSONString(filterGoodsInfo));
            log.info("check and import goods done: [{}ms]",
                    watch.getTime(TimeUnit.MILLISECONDS));
            return;
        }

        List<EcommerceGoods> ecommerceGoods = filterGoodsInfo.stream()
                .map(EcommerceGoods::to)
                .collect(Collectors.toList());

        List<EcommerceGoods> targetGoods = new ArrayList<>(ecommerceGoods.size());

        // 2. 保存goodsInfo之前判断是否存在重复商品(对比数据库)
        ecommerceGoods.forEach(g -> {

            // 判重
            if (ecommerceGoodsDao.findFirst1ByGoodsCategoryAndBrandCategoryAndGoodsName(
                    g.getGoodsCategory(),
                    g.getBrandCategory(),
                    g.getGoodsName()
            ).orElse(null) != null) {
                return;
            }

            targetGoods.add(g);
        });

        // 商品信息入库
        List<EcommerceGoods> savedGoods = IterableUtils.toList(
                ecommerceGoodsDao.saveAll(targetGoods)
        );

        // 还可以将入库商品信息同步到 redis 中
        saveNewGoodsInfoToRedis(savedGoods);
        log.info("save goods info to db and redis: [{}]", savedGoods.size());

        watch.stop();
        log.info("check and import goods success: [{}ms]",
                watch.getTime(TimeUnit.MILLISECONDS));
    }

    /**
     * 将保存到数据表中的数据缓存到redis中
     * dick: key -> <id, SimpleGoodsInfo(json)></>
     */
    private void saveNewGoodsInfoToRedis(List<EcommerceGoods> saveGoods) {
        // 由于redis是内存存储，不应该保存大量数据，只存储简单商品信息
        List<SimpleGoodsInfo> simpleGoodsInfos = saveGoods.stream()
                .map(EcommerceGoods::toSimple)
                .collect(Collectors.toList());

        Map<String, String> idToJSONObject = new HashMap<>(simpleGoodsInfos.size());
        simpleGoodsInfos.forEach(g -> idToJSONObject.put(g.getId().toString(), JSON.toJSONString(g)));

        // 保存到 Redis 中
        redisTemplate.opsForHash().putAll(
                GoodsConstant.ECOMMERCE_GOODS_DICT_KEY,
                idToJSONObject
        );
    }
}
